// moon: The build system and package manager for MoonBit.
// Copyright (C) 2024 International Digital Economy Academy
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// For inquiries, you can contact us via e-mail at jichuruanjian@idea.edu.cn.

use std::path::{Path, PathBuf};

use anyhow::Context;

use crate::common::TargetBackend;

pub struct MoonDirs {
    pub moon_home: PathBuf,
    pub moon_include_path: PathBuf,
    pub moon_lib_path: PathBuf,
    pub moon_bin_path: PathBuf,
    pub internal_tcc_path: PathBuf,
}

pub static MOON_DIRS: std::sync::LazyLock<MoonDirs> = std::sync::LazyLock::new(|| {
    let moon_home = home();
    let moon_include_path = moon_home.join("include");
    let moon_lib_path = moon_home.join("lib");
    let moon_bin_path = moon_home.join("bin");
    let internal_tcc_path = moon_bin_path.join("internal").join("tcc");
    MoonDirs {
        moon_home,
        moon_include_path,
        moon_lib_path,
        moon_bin_path,
        internal_tcc_path,
    }
});

pub fn home() -> PathBuf {
    if let Some(moon_home) = std::env::var_os("MOON_HOME") {
        PathBuf::from(moon_home)
    } else {
        let Some(h) = home::home_dir() else {
            eprintln!("Failed to get home directory");
            std::process::exit(1);
        };
        h.join(".moon")
    }
}

pub fn bin() -> PathBuf {
    home().join("bin")
}

pub fn lib() -> PathBuf {
    let lib = home().join("lib");
    if !lib.exists() {
        std::fs::create_dir_all(&lib).unwrap();
    }
    lib
}

pub fn core() -> PathBuf {
    let env_var = std::env::var_os("MOON_CORE_OVERRIDE");
    if let Some(path) = env_var {
        return PathBuf::from(path);
    }
    home().join("lib").join("core")
}

pub fn core_bundle(backend: TargetBackend) -> PathBuf {
    core()
        .join("target")
        .join(backend.to_dir_name())
        .join("release")
        .join("bundle")
}

pub fn core_packages_list(backend: TargetBackend) -> PathBuf {
    core()
        .join("target")
        .join(backend.to_dir_name())
        .join("release")
        .join("bundle")
        .join("packages.json")
}

// core.core & abort.core(virtual pkg default impl)
pub fn core_core(backend: TargetBackend) -> Vec<String> {
    vec![
        core_bundle(backend)
            .join("abort")
            .join("abort.core")
            .display()
            .to_string(),
        core_bundle(backend).join("core.core").display().to_string(),
    ]
}

pub fn cache() -> PathBuf {
    home().join("registry").join("cache")
}

pub fn index() -> PathBuf {
    home().join("registry").join("index")
}

/// Get the path of the index file of a package. [`base`] should be the path of
/// the index directory, for example, returned from [`index()`].
pub fn index_of_pkg(base: &Path, user: &str, pkg: &str) -> PathBuf {
    base.join("user")
        .join(user)
        .join(pkg)
        .with_extension("index")
}

pub fn credentials_json() -> PathBuf {
    home().join("credentials.json")
}

pub fn config_json() -> PathBuf {
    home().join("config.json")
}

pub fn moon_tmp_dir() -> anyhow::Result<PathBuf> {
    let p = home().join("tmp");
    if !p.exists() {
        std::fs::create_dir_all(&p)
            .with_context(|| format!("failed to create tmp directory `{}`", p.display()))?;
    }
    Ok(p)
}

#[test]
fn test_moon_dir() {
    use expect_test::expect;

    let dirs = [
        home(),
        core_bundle(TargetBackend::default()),
        cache(),
        index(),
        credentials_json(),
        config_json(),
        moon_tmp_dir().unwrap(),
    ];
    dbg!(&dirs);
    let dirs = dirs
        .iter()
        .map(|p| {
            p.strip_prefix(home())
                .unwrap()
                .to_str()
                .unwrap()
                .replace(['\\', '/'], "|")
        })
        .collect::<Vec<_>>();
    expect![[r#"
        [
            "",
            "lib|core|target|wasm-gc|release|bundle",
            "registry|cache",
            "registry|index",
            "credentials.json",
            "config.json",
            "tmp",
        ]
    "#]]
    .assert_debug_eq(&dirs);
}
